import os
import logging
import time
from datetime import datetime
from flask import Flask, jsonify, send_from_directory, request, Response
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app = Flask(__name__)

# 设置 JSON 响应不转义中文
app.json.ensure_ascii = False
app.json.indent = None


# ========================
# 日志模块配置
# ========================

def setup_logger(name, log_file='app.log', level=logging.INFO):
    """配置一个可复用的日志记录器"""
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - IP: %(ip)s - %(message)s')

    # 确保日志目录存在
    log_dir = 'logs'
    os.makedirs(log_dir, exist_ok=True)
    file_handler = logging.FileHandler(os.path.join(log_dir, log_file), encoding='utf-8')
    file_handler.setFormatter(formatter)

    console_handler = logging.StreamHandler()
    console_handler.setFormatter(formatter)

    logger = logging.getLogger(name)
    logger.setLevel(level)
    logger.addHandler(file_handler)
    logger.addHandler(console_handler)

    return logger

# 创建日志记录器
logger = setup_logger("resource_sharing", "app.log")


# ========================
# 请求钩子（记录请求日志）
# ========================

@app.before_request
def before_request():
    request.start_time = datetime.now()
    request.user_ip = request.remote_addr


@app.after_request
def after_request(response):
    ip = request.user_ip
    method = request.method
    path = request.path
    status = response.status
    duration = (datetime.now() - request.start_time).total_seconds() * 1000  # ms

    logger.info(
        f"请求完成 - 方法: {method}, 路径: {path}, 状态码: {status}, 耗时: {duration:.2f}ms",
        extra={'ip': ip}
    )
    return response


@app.errorhandler(404)
def not_found_error(error):
    ip = request.remote_addr
    logger.error("资源不存在", extra={'ip': ip})
    return jsonify({"error": "资源不存在"}), 404


@app.errorhandler(403)
def forbidden_error(error):
    ip = request.remote_addr
    logger.error("非法路径访问", extra={'ip': ip})
    return jsonify({"error": "非法路径访问"}), 403


@app.errorhandler(429)
def rate_limit_handler(e):
    ip = request.remote_addr
    logger.error("请求频率过高，请稍后再试", extra={'ip': ip})
    return jsonify({
        "error": "请求频率过高，请稍后再试"
    }), 429


@app.errorhandler(500)
def internal_error(error):
    ip = request.remote_addr
    logger.error("服务器内部错误", extra={'ip': ip})
    return jsonify({"error": "服务器内部错误"}), 500


# ========================
# 限流器配置
# ========================

limiter = Limiter(
    app=app,
    key_func=get_remote_address,
    default_limits=["200 per day", "50 per hour"],
    storage_uri="memory://",
)


# ========================
# 资源目录配置
# ========================

# 设置资源目录根路径（绝对路径）
RESOURCE_ROOT = os.path.abspath("resources")


def is_safe_path(basedir, path):
    """确保路径在指定目录内，防止路径穿越"""
    return os.path.abspath(path).startswith(os.path.abspath(basedir))


def build_tree(path, root):
    """递归构建目录树结构"""
    tree = {"name": os.path.basename(path), "type": "folder", "children": []}
    try:
        for item in sorted(os.listdir(path)):
            full_path = os.path.join(path, item)
            if os.path.isdir(full_path):
                tree["children"].append(build_tree(full_path, root))
            else:
                tree["children"].append({
                    "name": item,
                    "type": "file",
                    "path": os.path.relpath(full_path, root)
                })
    except Exception as e:
        ip = request.remote_addr
        logger.error(f"构建目录树失败: {e}", extra={'ip': ip})
    return tree


# ========================
# 路由定义
# ========================

@app.route('/')
def serve_index():
    return send_from_directory('./webui', 'index.html')


@app.route("/api/tree")
def get_tree():
    path = request.args.get("path", "")
    full_path = os.path.join(RESOURCE_ROOT, path)

    if not os.path.exists(full_path):
        ip = request.remote_addr
        logger.error(f"资源目录不存在: {full_path}", extra={'ip': ip})
        return jsonify({"error": "资源目录不存在"}), 404

    tree = build_tree(full_path, RESOURCE_ROOT)
    return jsonify(tree)


from urllib.parse import quote

@app.route("/download/<path:filepath>")
@limiter.limit("10/minute")
def download(filepath):
    full_path = os.path.abspath(os.path.join(RESOURCE_ROOT, filepath))

    if not is_safe_path(RESOURCE_ROOT, full_path):
        ip = request.remote_addr
        logger.error(f"非法路径访问: {full_path}", extra={'ip': ip})
        return jsonify({"error": "非法路径访问"}), 403

    if not os.path.isfile(full_path):
        ip = request.remote_addr
        logger.error(f"文件不存在: {full_path}", extra={'ip': ip})
        return jsonify({"error": "文件不存在"}), 404

    # 如果是预检请求，直接返回成功状态码
    if request.headers.get('X-Check-Limit') == 'true':
        return jsonify({"message": "可以下载"}), 200

    # 获取文件大小
    file_size = os.path.getsize(full_path)

    # 记录开始时间
    start_time = time.time()
    ip = request.remote_addr
    filename = os.path.basename(full_path)

    def log_download_complete():
        duration = time.time() - start_time
        speed_kb = (file_size / 1024) / duration if duration > 0 else 0
        logger.info(
            f"文件下载完成 - 文件名: {filename}, 大小: {file_size} bytes, 耗时: {duration:.2f}s, 下载速度: {speed_kb:.2f} KB/s",
            extra={'ip': ip}
        )

    def generate():
        try:
            chunk_size = 1024  # 1KB per chunk
            rate_limit = 50 * 1024  # 50KB/s
            chunks_per_second = rate_limit // chunk_size

            with open(full_path, "rb") as f:
                start_time_inner = time.time()
                chunk_count = 0
                while True:
                    chunk = f.read(chunk_size)
                    if not chunk:
                        break
                    yield chunk
                    chunk_count += 1
                    # 控制每秒发送的 chunk 数量
                    if chunk_count >= chunks_per_second:
                        elapsed = time.time() - start_time_inner
                        if elapsed < 1:
                            time.sleep(1 - elapsed)
                        start_time_inner = time.time()
                        chunk_count = 0
        except Exception as e:
            ip = request.remote_addr
            logger.error(f"文件读取失败: {e}", extra={'ip': ip})

    # === 中文文件名安全编码处理 ===
    try:
        # 使用 quote 编码 UTF-8 文件名，适用于 RFC 5987
        utf8_filename = quote(filename)
        ascii_filename = filename.encode('ascii', 'ignore').decode('ascii')
        if not ascii_filename:  # 防止全为非ASCII字符导致空文件名
            ascii_filename = "download.bin"

        # 设置 Content-Disposition：兼容性最好的方式
        response = Response(generate(), mimetype="application/octet-stream")
        response.headers["Content-Length"] = str(file_size)

        # 同时设置标准 filename 和扩展的 filename*=UTF-8''
        response.headers["Content-Disposition"] = f"attachment; filename=\"{ascii_filename}\"; filename*=UTF-8''{utf8_filename}"
    except Exception as e:
        ip = request.remote_addr
        logger.error(f"设置下载头部失败: {e}", extra={'ip': ip})
        return jsonify({"error": "服务器内部错误"}), 500

    # 注册回调
    response.call_on_close(log_download_complete)

    return response


# ========================
# 启动入口
# ========================

if __name__ == "__main__":
    app.run(debug=True)
